﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using EsbMonitorMessages;
using log4net;
using NServiceBus;
using NServiceBus.Host.Internal;
using NServiceBus.Unicast;
using NServiceBus.Unicast.Transport;

namespace EsbMonitorExtension
{
    public class InitializeEsbMonitor : IWantToRunAtStartup
    {
        private static readonly ILog Logger = LogManager.GetLogger(typeof (InitializeEsbMonitor));
        public IBus Bus { get; set; }

        #region IWantToRunAtStartup Members

        public void Run()
        {
            Logger.Info("Initializing Esb Monitoring...");

            UnicastBus unicastBus = UnicastBus();

            PerformUniversalInitDuties(unicastBus);

            PerformPublisherSpecificInitDuties(unicastBus);
        }

        public void Stop()
        {
        }

        #endregion

        private UnicastBus UnicastBus()
        {
            var unicastBus = Bus as UnicastBus;

            if (unicastBus == null)
            {
                throw new ApplicationException("UnicastBus is not present");
            }
            return unicastBus;
        }

        private void PerformUniversalInitDuties(UnicastBus unicastBus)
        {
            Logger.Info("Performing Universal Init Duties...");

            foreach (String messageOwner in unicastBus.MessageOwners.Keys)
            {
                if (!messageOwner.StartsWith("NServiceBus") && !messageOwner.StartsWith("EsbMonitorMessages"))
                {
                    Assembly assembly = Assembly.Load(messageOwner);
                    RaiseMessageAssemblyLoadedEvent(assembly);
                }
            }
        }

        private void PerformPublisherSpecificInitDuties(UnicastBus unicastBus)
        {
            if (IsPublisherEndpointPresent())
            {
                Logger.Info("Performing Publisher Specific Init Duties...");


                FieldInfo t = typeof (UnicastBus).GetField("transport",
                                                           BindingFlags.Public | BindingFlags.NonPublic |
                                                           BindingFlags.Instance);

                var transport = t.GetValue(unicastBus) as ITransport;
                string publisherInputQueueName = transport.Address;

                RaisePublisherStartedEvent(publisherInputQueueName);

                unicastBus.ClientSubscribed +=
                    (sender, e) => UnicastBus_OnClientSubscribed(sender, publisherInputQueueName, e);
            }
        }

        private void RaisePublisherStartedEvent(string publisherInputQueueName)
        {
            Bus.Send<IPublisherStartedEvent>(m =>
                                                 {
                                                     m.InputQueueName = publisherInputQueueName;
                                                     m.Time = DateTime.Now;
                                                 });
        }


        private void RaiseMessageAssemblyLoadedEvent(Assembly assembly)
        {
            string fullName = assembly.FullName;
            string location = assembly.Location;
            string fileName = assembly.ManifestModule.ScopeName;
            Guid moduleVersionId = assembly.ManifestModule.ModuleVersionId;

            byte[] binaryData = File.ReadAllBytes(location);

            Bus.Send<IMessageAssemblyLoadedEvent>(m =>
                                                      {
                                                          m.FileName = fileName;
                                                          m.FullName = fullName;
                                                          m.ModuleVersionId = moduleVersionId;
                                                          m.BinaryData = binaryData;
                                                      }
                );
        }

        private bool IsPublisherEndpointPresent()
        {
            IEnumerable<Type> endpoints = ScanAssembliesForEndpoints();
            foreach (Type endpoint in endpoints)
            {
                if (typeof (AsA_Publisher).IsAssignableFrom(endpoint))
                {
                    return true;
                }
            }

            return false;
        }

        private static IEnumerable<Type> ScanAssembliesForEndpoints()
        {
            foreach (Assembly assembly in AssemblyScanner.GetScannableAssemblies())
            {
                foreach (Type type in assembly
                    .GetTypes()
                    .Where(
                    t => typeof (IConfigureThisEndpoint).IsAssignableFrom(t) && t != typeof (IConfigureThisEndpoint)))
                {
                    yield return type;
                }
            }
        }

        private void UnicastBus_OnClientSubscribed(object sender, String publisherInputQueueName,
                                                   SubscriptionEventArgs e)
        {
            Type type = Type.GetType(e.MessageType);
            Guid version = type.Assembly.ManifestModule.ModuleVersionId;

            Bus.Send<INewSubscriptionEvent>(m =>
                                                {
                                                    m.PublisherQueueName = publisherInputQueueName;
                                                    m.SubscriberQueueName = e.SubscriberAddress;
                                                    m.TypeName = e.MessageType;
                                                    m.MessageAssemblyVersionId = version.ToString();
                                                    m.Time = DateTime.Now;
                                                });
        }
    }
}